﻿using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Miris.HumanMilkAnalyser.DataModel;
using Miris.HumanMilkAnalyser.Repository.Interface;
using Miris.HumanMilkAnalyser.Service.Interface;
using Miris.HumanMilkAnalyser.Service.Shared;
using Miris.HumanMilkAnalyser.Service.Validation;

namespace Miris.HumanMilkAnalyser.Service
{
    public class DataSourceManagementService
    {
        public const string ExportDataSourceFileExtension = ".Miris.Human.Milk.Analyser.ExportedDataSource";
        public const string ExportPatientDataFileExtension = ".Miris.Human.Milk.Analyser.ExportedPatientData";
        public const string ExportTestReportFileExtension = ".csv";
        public const string ExportMiniLogFileExtension = ".txt";
        protected IRepositoryFactory RepositoryFactory;

        enum ImportAction
        {
            Add,
            AddOrDuplicate
        }

        public DataSourceManagementService(IRepositoryFactory repositoryFactory)
        {
            RepositoryFactory = repositoryFactory;
        }

        /// <summary>
        /// Export current data source file to configured location.
        /// </summary>
        /// <returns>The data source file export full name.</returns>
        public virtual string ExportDataSourceFile()
        {
            var dataSourceFileFullName = ConfigFileUtility.GetDataSourceFileFullName(ConfigurationConstant.ConnectionName);
            var dataSourceFileExportDirectoryFullName = ConfigFileUtility.GetDataSourceFileExportPathFullName();
            var exportedDataSourceFileName = GeneralUtility.GetCurrentDateTime().ToString(GeneralUtility.FileNameTimestampFormat) + ExportDataSourceFileExtension;
            var dataSourceFileExportFullName = Path.Combine(dataSourceFileExportDirectoryFullName, exportedDataSourceFileName);

            File.Copy(dataSourceFileFullName, dataSourceFileExportFullName);

            return dataSourceFileExportFullName;
        }

        /// <summary>
        /// Import the selected data source file and replace the current one. Before importing 
        /// system will auto export current data source file first.
        /// </summary>
        /// <param name="selectedDataSourceFileFullName">The selected data source file full name for importing.</param>
        /// <returns>If importing is done successfully, the exported data source file full name will be returned.
        /// If importing failed, null will be returned.</returns>
        public virtual string ImportDataSourceFile(string selectedDataSourceFileFullName)
        {
            var exportedDataSourceFileFullName = ExportDataSourceFile();
            var dataSourceFileFullName = ConfigFileUtility.GetDataSourceFileFullName(ConfigurationConstant.ConnectionName);

            if (File.Exists(selectedDataSourceFileFullName))
            {
                File.Copy(selectedDataSourceFileFullName, dataSourceFileFullName, true);

                return exportedDataSourceFileFullName;
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Export data source to xml file.
        /// </summary>
        /// <returns></returns>
        public virtual string ExportDataSourceToXMLFile()
        {
            var patientDataXmlFilePathFullName = ConfigFileUtility.GetPatientDataExportPathFullName();
            var exportedPatientDataXmlFileName = GeneralUtility.GetCurrentDateTime().ToString(GeneralUtility.FileNameTimestampFormat) + ExportPatientDataFileExtension;
            var exportedPatientDataXmlFileFullName = Path.Combine(patientDataXmlFilePathFullName, exportedPatientDataXmlFileName);
            var dataSet = new DataSet();
            dataSet.DataSetName = "Miris.HumanMilkAnalyser.Data";

            using (RepositoryFactory)
            {
                RepositoryFactory.OpenSession();

                dataSet.Tables.Add(DataSetHelper.CreateFillDataTable<Patient>
                    (RepositoryFactory.CreateRepositoryBase<Patient>()));

                dataSet.Tables.Add(DataSetHelper.CreateFillDataTable<AnalysisResult>
                    (RepositoryFactory.CreateRepositoryBase<AnalysisResult>()));

                dataSet.Tables.Add(DataSetHelper.CreateFillDataTable<AnalysisResultDiagnosticMessage>
                    (RepositoryFactory.CreateRepositoryBase<AnalysisResultDiagnosticMessage>()));
            }

            dataSet.WriteXml(exportedPatientDataXmlFileFullName);

            return exportedPatientDataXmlFileFullName;
        }

        /// <summary>
        /// Import selected XML file that contains all Patient and related data into data source.
        /// </summary>
        /// <param name="selectedPatientDataXmlFileFullName">The selected Patient data xml file full name.</param>
        public virtual void ImportXMLFileToDataSource(string selectedPatientDataXmlFileFullName)
        {
            var dataSet = new DataSet();
            dataSet.ReadXml(selectedPatientDataXmlFileFullName);

            using (RepositoryFactory)
            {
                RepositoryFactory.OpenSessionWithTransaction();

                // Import Patient data
                var patientNumberMapping = ImportOrDuplicateData<Patient>(dataSet, "Number", new PatientValidator());

                // Import the AnalysisResult data
                var analysisResultKeyMapping = ImportOrDuplicateData<AnalysisResult>(dataSet, "SampleNumber", patientNumberMapping,
                    "PatientNumber", new AnalysisResultValidator(), ImportAction.AddOrDuplicate);

                // Import AnalysisResultDiagnosticMessage data
                ImportOrDuplicateData<AnalysisResultDiagnosticMessage>(dataSet, analysisResultKeyMapping, "SampleNumber",
                    new AnalysisResultDiagnosticMessageValidator());

                // Commit the change
                RepositoryFactory.Commit();
            }
        }

        /// <summary>
        /// Clean entire data source.
        /// </summary>
        public virtual void CleanDataSource()
        {
            using (RepositoryFactory)
            {
                RepositoryFactory.OpenSessionWithTransaction();
                var repository = RepositoryFactory.CreatePatientRepository();

                repository.ExecuteSqlCommand("DELETE FROM " + (typeof(AnalysisResultDiagnosticMessage)).Name);
                repository.ExecuteSqlCommand("DELETE FROM " + (typeof(AnalysisResult)).Name);
                repository.ExecuteSqlCommand("DELETE FROM " + (typeof(Patient)).Name);
                repository.ExecuteSqlCommand("DELETE FROM " + (typeof(DiagnosticMessageReference)).Name);

                RepositoryFactory.Commit();
            }
        }

        /// <summary>
        /// Import or duplicate data with need of update navigation relationship.
        /// </summary>
        /// <typeparam name="T">The data entity.</typeparam>
        /// <param name="dataSet">The DataSet that contains the DataTable named typeof(T)).Name.</param>
        /// <param name="primaryKeyName">The entity primary key name. For ImportAction.ImportOrDuplicate use ONLY.</param>
        /// <param name="primaryKeyMapping">The primary key mapping. The client can get the updated primary key value by accessing 
        /// this Dictionary using original primary key value.</param>
        /// <param name="navigationKeyName">The navigation key name.</param>
        /// <param name="dataValidator">The data validator implements IDataValidator.</param>
        /// <param name="action">Which action will be taken.</param>
        /// <returns>The primary key mapping. The client can get the updated primary key value by accessing 
        /// this Dictionary using original primary key value.</returns>
        private Dictionary<string, string> ImportOrDuplicateData<T>(DataSet dataSet,
            string primaryKeyName, Dictionary<string, string> primaryKeyMapping,
            string navigationKeyName, IDataValidator<T> dataValidator, ImportAction action) where T : class
        {
            // Transfer the data from DataTable to Data list.
            var entities = DataSetHelper.TransferDataFromDataTableToDataModel<T>(dataSet.Tables[(typeof(T)).Name]);
            var primaryKeyOutputMapping = new Dictionary<string, string>();

            // If need to update the relationship
            if (primaryKeyMapping != null && primaryKeyMapping.Count > 0 && !string.IsNullOrEmpty(navigationKeyName))
            {
                // Get the new navigation property value and update the relationship
                foreach (var entity in entities)
                {
                    GeneralUtility.SetPropertyValue(entity, navigationKeyName,
                        primaryKeyMapping[(string)GeneralUtility.GetPropertyValue(entity, navigationKeyName)]);
                }
            }

            if (action == ImportAction.AddOrDuplicate)
            {
                // Add or dupilicate the entities
                CommonService.AddOrDuplicate<T>(RepositoryFactory.CreateRepositoryBase<T>(), entities,
                    primaryKeyName, dataValidator, primaryKeyOutputMapping);
            }
            else if (action == ImportAction.Add)
            {
                // Add the entities
                RepositoryFactory.CreateRepositoryBase<T>().Add(entities);
            }
            else
            {
                throw new NotSupportedException("Not supported ImportAction parameter deteced.");
            }

            return primaryKeyOutputMapping;
        }

        /// <summary>
        /// Import or duplicate data without need of update navigation relationship.
        /// </summary>
        /// <typeparam name="T">The data entity.</typeparam>
        /// <param name="dataSet">The DataSet that contains the DataTable named typeof(T)).Name.</param>
        /// <param name="primaryKeyName">The entity primary key name.</param>
        /// <param name="dataValidator">The data validator implements IDataValidator.</param>
        /// <returns>The primary key mapping. The client can get the updated primary key value by accessing 
        /// this Dictionary using original primary key value.</returns>
        private Dictionary<string, string> ImportOrDuplicateData<T>(DataSet dataSet, string primaryKeyName, IDataValidator<T> dataValidator) where T : class
        {
            return ImportOrDuplicateData<T>(dataSet, primaryKeyName, null, null, dataValidator, ImportAction.AddOrDuplicate);
        }

        /// <summary>
        /// Import data with need of update navigation relationship.
        /// </summary>
        /// <typeparam name="T">The data entity.</typeparam>
        /// <param name="dataSet">The DataSet that contains the DataTable named typeof(T)).Name.</param>
        /// <param name="primaryKeyMapping">The primary key mapping. The client can get the updated primary key value by accessing 
        /// this Dictionary using original primary key value.</param>
        /// <param name="navigationKeyName">The navigation key name.</param>
        /// <param name="dataValidator">The data validator implements IDataValidator.</param>
        /// <returns>The primary key mapping. The client can get the updated primary key value by accessing 
        /// this Dictionary using original primary key value.</returns>
        private Dictionary<string, string> ImportOrDuplicateData<T>(DataSet dataSet, Dictionary<string, string> primaryKeyMapping,
            string navigationKeyName, IDataValidator<T> dataValidator) where T : class
        {
            return ImportOrDuplicateData<T>(dataSet, null, primaryKeyMapping, navigationKeyName, dataValidator, ImportAction.Add);
        }

        /// <summary>
        /// Import data without need of update navigation relationship.
        /// </summary>
        /// <typeparam name="T">The data entity.</typeparam>
        /// <param name="dataSet">The DataSet that contains the DataTable named typeof(T)).Name.</param>
        /// <param name="dataValidator">The data validator implements IDataValidator.</param>
        private void ImportOrDuplicateData<T>(DataSet dataSet, IDataValidator<T> dataValidator) where T : class
        {
            ImportOrDuplicateData<T>(dataSet, null, null, null, dataValidator, ImportAction.Add);
        }
    }
}
